using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using TestCases.Assertions;

namespace TestCases.XmlAssert
{
    public interface IEAssertRoot
    {
        IEAssert Child { get; }
        IEAssert ThisElement { get; }
        IEAssert ChildAt(int position);

        IEAssert Element(string name);
        IEAssert Element(QualifyForm form, string name);
        IEAssert ElementAt(string name, int position);
        IEAssert ElementAt(QualifyForm form, string name, int position);
    }

    public interface IEAssert : IEAssertRoot
    {
        IAAssert With { get; }
        IEAssert Parent { get; }
        IEAssertRoot Root { get; }


        IDisposable RememberThis();

        IEAssert HasName(string name);
        IEAssert HasName(QualifyForm form, string name);
    }

    internal class ElementAssert : AssertBase, IEAssert
    {
        private readonly XContainer _root;
        private XContainer _current;
        private XContainer _thisElement;

        public ElementAssert(IAssertion assertion, XContainer root) : base(assertion)
        {
            _current = _root = root;
            QualifyForm = QualifyForm.Qualified;
            AttributeForm = QualifyForm.Unqualified;
        }

        public QualifyForm AttributeForm { get; set; }

        public XContainer Container
        {
            get { return _root; }
        }

        public IEAssert Child
        {
            get
            {
                IEnumerable<XElement> elements = _current.Elements();
                Assert(elements.Count() > 0, "Current node doesn't contains any elements.");
                _current = elements.ElementAt(0);
                return this;
            }
        }

        public IEAssert ChildAt(int position)
        {
            IEnumerable<XElement> elements = _current.Elements();
            Assert(position >= 0 && position < elements.Count(),
                "Current node doesn't contain element at <{0}> position.", position);
            _current = elements.ElementAt(position);
            return this;
        }

        public IEAssert Element(string name)
        {
            return (CurrentIsRoot())
                ? Element(QualifyForm.Qualified, name)
                : Element(QualifyForm, name);
        }

        public IEAssert Element(QualifyForm form, string name)
        {
            XElement element = _current.Element(GetName(form, name));
            Assert(element != null, "Current node doesn't contain element with name <{0}>.", name);
            _current = element;
            return this;
        }

        public IEAssert ElementAt(string name, int position)
        {
            return (CurrentIsRoot())
                ? ElementAt(QualifyForm.Qualified, name, position)
                : ElementAt(QualifyForm, name, position);
        }

        public IEAssert ElementAt(QualifyForm form, string name, int position)
        {
            IEnumerable<XElement> elements = _current.Elements(GetName(form, name));
            Assert(position >= 0 && position < elements.Count(),
                "Current node doesn't contain element <{0}> at <{1}> position.", name, position);
            _current = elements.ElementAt(position);
            return this;
        }

        public IEAssert HasName(string name)
        {
            return (CurrentIsRootElement())
                ? HasName(QualifyForm.Qualified, name)
                : HasName(QualifyForm, name);
        }

        public IEAssert HasName(QualifyForm form, string name)
        {
            Assert(CurrentIsElement(), "Current node is not element.");
            var element = (XElement) _current;
            XName xName = GetName(form, name);
            Assert(element.Name.Equals(xName), "Current node name is invalid. Expected:<{0}>. Actual:<{1}>.", xName, element.Name);
            return this;
        }

        public IEAssert ThisElement
        {
            get
            {
                Assert(_thisElement != null, "There is no one 'This' element, before use 'This' element call RememberThis()");
                _current = _thisElement;
                return this;
            }
        }

        public IDisposable RememberThis()
        {
            return new RememberItem(this);
        }

        public IAAssert With
        {
            get
            {
                Assert(CurrentIsElement(), "Current node is not element.");
                return new AttributeAssert(AssertionMethod, (XElement) _current)
                {
                    Namespace = Namespace,
                    QualifyForm = AttributeForm
                };
            }
        }

        public IEAssert Parent
        {
            get
            {
                Assert(!CurrentIsRoot(), "Current node is root");
                _current = _current.Parent ?? _root;
                return this;
            }
        }

        public IEAssertRoot Root
        {
            get
            {
                _current = _root;
                return this;
            }
        }

        private bool CurrentIsRoot()
        {
            return ReferenceEquals(_current, _root);
        }

        private bool CurrentIsRootElement()
        {
            return ReferenceEquals(_current.Parent, _root);
        }

        private bool CurrentIsElement()
        {
            return _current is XElement;
        }

        private class RememberItem : IDisposable
        {
            private readonly ElementAssert _assert;
            private readonly XContainer _previousElement;

            public RememberItem(ElementAssert assert)
            {
                _previousElement = assert._thisElement;
                assert._thisElement = assert._current;
                _assert = assert;
            }

            public void Dispose()
            {
                _assert._thisElement = _previousElement;
            }
        }
    }
}